package com.chiyoyo.shiro.framework;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.AccessControlFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * 自定义访问控制过滤器，继承自 Apache Shiro 的 AccessControlFilter 类。
 * 该过滤器用于处理请求的访问控制逻辑，包括 OPTIONS 请求的直接放行和基于 Authorization 头的认证。
 *
 * @author chil
 * @since 2024/8/6
 */
@Slf4j
public class MyAccessControlFilter extends AccessControlFilter {

    /**
     * 判断请求是否允许访问。
     * 如果请求方法为 OPTIONS，则直接放行；否则，拒绝访问。
     *
     * @param servletRequest  当前请求对象
     * @param servletResponse 当前响应对象
     * @param o               可选参数，这里未使用
     * @return 如果请求方法为 OPTIONS 则返回 true，否则返回 false
     * @throws Exception 可能抛出的异常
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        // 如果是 OPTIONS 请求，直接放行
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
            return "OPTIONS".equalsIgnoreCase(httpRequest.getMethod());
        }
        return false;
    }

    /**
     * 当请求被拒绝访问时调用此方法。
     * 尝试从请求头中获取 Authorization 信息进行登录验证。
     * 如果登录成功，则允许访问；否则，记录日志并返回登录失败信息。
     *
     * @param servletRequest  当前请求对象
     * @param servletResponse 当前响应对象
     * @return 如果登录成功则返回 true，否则返回 false
     * @throws Exception 可能抛出的异常
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String token = httpServletRequest.getHeader("Authorization");
        try {
            getSubject(servletRequest, servletResponse).login(new MyAuthenticationToken(token));
            return true;
        } catch (Exception e) {
            String requestUrl = httpServletRequest.getRequestURI().replaceAll("//", "/");
            log.warn("request_url:[{}], login err, token {}", requestUrl, token);
            onLoginFail(servletResponse);
            return false;
        }
    }

    /**
     * 当登录失败时调用此方法。
     * 设置响应状态码为 200，内容类型为 application/json，并返回登录失败的 JSON 消息。
     *
     * @param response 当前响应对象
     */
    private void onLoginFail(ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_OK);
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json; charset=utf-8");
        try {
            httpResponse.getWriter().write("{\"code\":4003,\"msg\":\"未登录或登录超时，请重新登录\"}");
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
